#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
/*
 *  write a file to spi_flash
 */
#define W25_WRITE_ENABLE 0x06
#define W25_WRITE_DISABLE 0x04
#define W25_READ_STATUS_1 0x05
#define W25_READ_STATUS_2 0x35
#define W25_WRITE_STATUS 0x01
#define w25_WRITE_SATTUS_ENABLE 0x50;
#define W25_READ_DATA 0x03
#define W25_READ_FAST_DATA 0x0B
#define W25_ERASE_SECTOR 0x20
#define W25_ERASE_BLOCK 0xD8
#define W25_ERASE_CHIP 0xc7
#define W25_PAGE_PROGRAM 0x02
#define W25_CHIP_ID 0x9F
#define RDSR_CNT 10

#define W25_SR_BUSY 0x01
#define FLASH_MAX_SIZE (8 * 1024 * 1024)
#define ONE_SELECTOR_SIZE (4 * 1024)
#define WRITE_READ_ONCE_SIZE 256
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

static const char *spi_device = "/dev/spidev2.2";
static unsigned char mode;
static unsigned char bits = 8;
static unsigned int speed = 3000000;
static unsigned short delay;
static unsigned char buf[256] = {0};
static unsigned char rxbuffer[1];
static unsigned char txbuffer[260];

static int getFileSize(const char *name);
static void dis_array(const char *info, const unsigned char *buf, unsigned int len);
static int spi_write_read(int fd, const char *wbuf, unsigned int wlen,
						  const char *rbuf, unsigned int rlen);
static int wait_for_idle(int fd);
static int write_spiflash(int fd, int address, char *buf, int num);
static void read_spiflash(int fd, int address, unsigned char *rd_buf, int len);
static void write_enable(int fd);
static void sector_erase(int fd, int address);
static void print_chipID(int fd);
static void write_status_register(int fd);

/*
 * 得到文件的大小
 */
static int getFileSize(const char *name)
{
	FILE *pFile = fopen(name, "rb+");
	if (pFile == NULL)
	{
		printf("cout error\n");
		return -1;
	}
	fseek(pFile, 0, SEEK_END);
	int length = ftell(pFile);
	fclose(pFile);

	return length;
}
/*
 * 打印出buf的内容
 * @parm info 前面的提示信息
 */
static void dis_array(const char *info, const unsigned char *buf, unsigned int len)
{
	int i = 0;
	printf("%s :", info);
	for (i = 0; i < len; i++)
	{
		printf("%02x ", buf[i]);
	}
	printf("\n");
}
/*
 *  spi write and read
 */
static int spi_write_read(int fd, const char *wbuf, unsigned int wlen,
						  const char *rbuf, unsigned int rlen)
{

	int ret = 0;
	struct spi_ioc_transfer tr[2] = {
		{
			.tx_buf = (unsigned long)wbuf,
			.rx_buf = 0,
			.len = wlen,
			.speed_hz = speed,
		},
		{
			.tx_buf = 0,
			.rx_buf = (unsigned long)rbuf,
			.len = rlen,
			.speed_hz = speed,
		},
	};

	ret = ioctl(fd, SPI_IOC_MESSAGE(2), tr);

	return ret;
}
/*
 * 在进行操作前进行忙判断
 */
static int wait_for_idle(int fd)
{
	int i = 0;
	unsigned char strg = 0;
	unsigned char tempcmd = W25_READ_STATUS_1;
	int ret = 0;

	for (i = 0; i < RDSR_CNT; i++)
	{
		ret = spi_write_read(fd, &tempcmd, 1, &strg, 1);
		if (ret < 0)
		{
			printf("get status register value error \n");
			goto exit;
		}
		if (strg & W25_SR_BUSY)
		{
			printf("wait for idle ...\n");
			sleep(1);
			continue;
		}
		else
		{
			break;
		}
	}
	if (i == RDSR_CNT)
	{
		printf("/dev/spidev2.2 is too busy now \n");
		ret = -1;
		goto exit;
	}
	return 0;
exit:
	return ret;
}

/*
 * write data to spi less than 256
 */
static int write_spiflash(int fd, int address, char *buf, int num)
{
	txbuffer[0] = W25_PAGE_PROGRAM;
	txbuffer[1] = (address >> 16) & 0xff;
	txbuffer[2] = (address >> 8) & 0xff;
	txbuffer[3] = address & 0xff;
	for (int i = 0; i < num; i++)
	{
		txbuffer[i + 4] = buf[i];
	}
	wait_for_idle(fd);
	write_enable(fd);
	spi_write_read(fd, txbuffer, (4 + num), rxbuffer, 0);
	printf("write  sector  %06x  ok \n", address);
}

/*
 * read data from spi 256
 */
static void read_spiflash(int fd, int address, unsigned char *rd_buf, int len)
{
	unsigned char tempcmd[4] = {0};
	wait_for_idle(fd);
	tempcmd[0] = W25_READ_DATA;
	tempcmd[1] = (address >> 16) & 0xff;
	tempcmd[2] = (address >> 8) & 0xff;
	tempcmd[3] = address & 0xff;
	spi_write_read(fd, tempcmd, 4, rd_buf, len);
	dis_array("Read bytes", rd_buf, len);
}
/*
 *  在檫除，写数据之前进行写使能操作
 */
static void write_enable(int fd)
{
	unsigned char tempcmd = W25_WRITE_ENABLE;
	write(fd, &tempcmd, sizeof(tempcmd));
}
/*
 *  关闭写使能操作
 */
static void write_disable(int fd)
{
	unsigned char tempcmd = W25_WRITE_DISABLE;
	write(fd, &tempcmd, sizeof(tempcmd));
}
/*
 *  檫除4KB
 *  @parm address   檫除地址的A23-A0
 */
static void sector_erase(int fd, int sector_address)
{
	unsigned char tempcmd[4] = {0};
	wait_for_idle(fd);
	write_enable(fd);
	tempcmd[0] = W25_ERASE_SECTOR;
	tempcmd[1] = (sector_address >> 16) & 0xff;
	tempcmd[2] = (sector_address >> 8) & 0xff;
	tempcmd[3] = sector_address & 0xff;
	write(fd, tempcmd, 4);
	printf("Erase_Sector:      %06x  selector erase done ..\n", address);
}
/*
 *  打印设备信息
 */
static void print_chipID(int fd)
{
	unsigned char tempbuf[3] = {0};
	unsigned char tempcmd = W25_CHIP_ID;
	spi_write_read(fd, &tempcmd, 3, tempbuf, 3);
	dis_array("chip_ID: ManufactureID  MemoryTypeID  CapacityID  ", tempbuf, 3);
}
/*
 * 在初始化时调用，写状态寄存器
 */
static void write_status_register(int fd)
{
	unsigned char tempcmd[3] = {0};
	wait_for_idle(fd);
	write_enable(fd);
	tempcmd[0] = W25_WRITE_STATUS;
	tempcmd[1] = 0;
	tempcmd[2] = 0;
	write(fd, tempcmd, 3);
}

int main(int argc, char *argv[])
{
	int ret = 0;
	int fd;
	char *fileName;
	int erase_selector_number = 0;
	int write_selector_number = 0;

	if (argc != 2)
	{
		printf("Usage:		%s        %s\n", argv[0], "fileName");
		return -1;
	}
	else
	{
		fileName = argv[1];
	}

	fd = open(spi_device, O_RDWR);
	if (fd < 0)
	{
		printf("open /dev/spidev error\n ");
	}

	/*
	 * spi mode
	 */
	ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
	if (ret == -1)
	{
		printf("set spi mode error\n");
	}
	ret = ioctl(fd, SPI_IOC_RD_MODE, &mode);
	if (ret == -1)
	{
		printf("get spi mode error\n");
	}

	/*
	 *  bits per word
	 */
	ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
	if (ret == -1)
	{
		printf("set bits per word error\n");
	}
	ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
	if (ret == -1)
	{
		printf("get bits per word error\n");
	}

	/*
	 * max speed hz
	 */
	ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
	if (ret == -1)
	{
		printf("set max speed error\n");
	}
	ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
	if (ret == -1)
	{
		printf("get max speed error\n");
	}

	/*
	 * print useful info
	 */
	printf("spi mode: %d\n", mode);
	printf("spi bits per words : %d \n", bits);
	printf("spi max speed : %dhz (%dkHz)\n", speed, speed / 1000);

	/*
	 *  open written file covert to bytes[]
	 */
	int length = getFileSize(fileName);
	//大于8M 就退出
	if (length > FLASH_MAX_SIZE)
	{
		printf("The Flash Max Size is 8*1024*1024,your written file size = %dM\n", length / 1024 / 1024);
		return -2;
	}
	FILE *filefd;
	filefd = fopen(fileName, "rw+");
	unsigned char filebuf[length];
	fread(filebuf, sizeof(char), length, filefd);
	fclose(filefd);
	printf("Written file size =  %d \n", length);
	// dis_array("Written_file_info", filebuf , sizeof(filebuf));

	/*
	 * get chip id
	 */
	print_chipID(fd);
	// write status register
	write_status_register(fd);

	/*
	 *once erase suitable selector
	 */
	erase_selector_number = (length / ONE_SELECTOR_SIZE + 1);
	printf("Now begin erase foresee size = %d ,  please waiting... \n", erase_selector_number);
	int seletor_times = 0;
	for (int i = 0; i < erase_selector_number; i++)
	{
		sector_erase(fd, i);
		seletor_times++;
	}
	printf("Erase subitable selector done , erase_times = %d\n", seletor_times);

	/*
	 * now begin write
	 */
	write_selector_number = length / WRITE_READ_ONCE_SIZE + 1;
	int all_selector_write = write_selector_number - 1;
	int finally_size = length % WRITE_READ_ONCE_SIZE;
	printf("now begin write , write_selector_number = %d\n", write_selector_number);
	int write_times = 0;
	for (int i = 0; i < write_selector_number; i++)
	{
		if (i < all_selector_write)
		{
			write_spiflash(fd, i, filebuf + i * 256, 256);
		}
		else
		{
			write_spiflash(fd, i, filebuf + i * 256, finally_size);
		}
		write_times++;
	}
	printf("writed  done , write_times = %d\n", write_times);

	/*
	 * now read to test
	 */
	for (int i = 0; i < write_selector_number; i++)
	{
		read_spiflash(fd, i, buf, 256);
	}
	close(fd);

	return ret;

exit:
	printf("error exit........................\n");
	return ret;
}
